#include "freyapublicregister.h"
#include "freyabaseextension.h"
#include <QtMath>

FreyaPluginPusher::FreyaPluginPusher(QString PluginID, FreyaBaseControl *pControl) :
    QThread(NULL), m_MsgAuth(0), m_CmdAuth(0), m_PluginID(PluginID), m_FreyaControl(pControl)
{
    qDebug()<<"FreyaLib > "<<"FreyaPluginPusher:ID:"<<PluginID;

    moveToThread(this);
    start();

    connect(this, &FreyaPluginPusher::ToPusherWrite, this, &FreyaPluginPusher::OnPusherWrite, Qt::QueuedConnection);
    connect(this, &FreyaPluginPusher::ToInitPusher, this, &FreyaPluginPusher::OnInitPusher, Qt::QueuedConnection);

    emit ToInitPusher();
}

void FreyaPluginPusher::PusherExecute(const FreyaData data)
{
    if(m_MsgAuth & data->command)
    {
        emit ToPusherWrite(data);
    }
}

QString &FreyaPluginPusher::FreyaPluginID()
{
    return m_PluginID;
}

bool FreyaPluginPusher::PusherWrite(const FreyaData data)
{
    QByteArray &&serializeData = FreyaBaseData::Serialize(data);
    QByteArray &&compressData = qCompress(serializeData, 9);

    if(m_Pusher->write(compressData) != compressData.size()/* || !m_Pusher->flush()*/)
    {
        qWarning() << "FreyaLib > " << "PusherExcute: write error" << (int)m_Pusher->error() << m_Pusher->errorString() << data->dataID;
//        m_Pusher->disconnectFromServer();
//        m_Pusher->connectToServer(m_PluginID, QIODevice::ReadWrite);
        return false;
    }

    return true;
}

void FreyaPluginPusher::OnInitPusher()
{
    m_PusherLoop = new QEventLoop(this);

    m_Pusher = new QLocalSocket(this);
    connect(m_Pusher, SIGNAL(stateChanged(QLocalSocket::LocalSocketState)), this, SLOT(OnStateChanged(QLocalSocket::LocalSocketState)), Qt::QueuedConnection);
    connect(m_Pusher, SIGNAL(connected()), this, SLOT(OnConnected()), Qt::QueuedConnection);
    connect(m_Pusher, SIGNAL(disconnected()), this, SLOT(OnDisconnected()), Qt::QueuedConnection);
    m_Pusher->connectToServer(m_PluginID, QIODevice::ReadWrite);
    if(m_Pusher->waitForConnected(1000))
    {
        qDebug() << "FreyaLib > " << "FreyaPluginPusher:ID:" << m_PluginID << "SUCCESS!";
        connect(m_Pusher, SIGNAL(readyRead()), this, SLOT(OnReadyRead()));
        connect(m_Pusher, SIGNAL(disconnected()), this, SIGNAL(ToDisconnected()));
        PusherWrite(FreyaBaseData::CreateDate(FREYALIB_CMD_CONNECTRESULT));
    }
}

void FreyaPluginPusher::OnPusherWrite(const FreyaData data)
{
    QMutexLocker pusherLocker(&m_PusherMutex);
    Q_UNUSED(pusherLocker);

    m_LastDataId = data->dataID;
    int count = 3;

    while(count)
    {
        qDebug() << "FreyaLib > " << "PusherExcute:" << hex << "DataID:" << m_LastDataId << "To:" << m_PluginID;
        if(PusherWrite(data))
        {
//            QTimer::singleShot(500, m_PusherLoop, SLOT(quit()));
//            m_PusherLoop->exec();
//            if(m_LastDataId.isEmpty())
//            {
                break;
//            }
//            else
//            {
//                qWarning() << "FreyaLib > " << "PusherExcute: write timeout " << m_Pusher->errorString();
//            }
        }
        else
        {
            QThread::msleep(200);
        }
        --count;
    }

    if(!count)
    {
        qWarning() << "FreyaLib > " << "PusherExcute: write failed " << m_LastDataId;
    }

    m_LastDataId.clear();
}

void FreyaPluginPusher::OnReadyRead()
{
    QByteArray &&allBa = m_Pusher->readAll();
    FreyaData pData = FreyaBaseData::Unserialize(qUncompress(allBa));

    if(pData.isNull())
    {
        qWarning() << "FreyaLib > " << "PusherReadyRead: cannot unserialize data: " << allBa.toHex();
        return;
    }

    switch (pData->command)
    {
    case FREYALIB_CMD_PLUGINAUTHREQUEST:
    {
        QVariantMap AuthCodeMap = pData->GetProperty().toMap();
        QStringList MsgAuthList = AuthCodeMap.value(FREYALIB_TYP_PLUGINMSGAUTH).toStringList();
        QStringList CmdAuthList = AuthCodeMap.value(FREYALIB_TYP_PLUGINCMDAUTH).toStringList();
        foreach (const QString &MsgCode, MsgAuthList)
        {
            FreyaData pCodeData = m_FreyaControl->FindFreyaData(MsgCode);
            if(pCodeData)
            {
                m_MsgAuth = (m_MsgAuth | pCodeData->GetProperty().toInt());
            }
        }
        qDebug() << "FreyaLib > " << "PluginID:" << m_PluginID << "MsgAuth:" << hex << m_MsgAuth;
        foreach (const QString &CmdCode, CmdAuthList)
        {
            FreyaData pCodeData = m_FreyaControl->FindFreyaData(CmdCode);
            if(pCodeData)
            {
                m_CmdAuth = (m_CmdAuth | pCodeData->GetProperty().toInt());
            }
        }
        qDebug() << "FreyaLib > " << "PluginID:" << m_PluginID << "CmdAuth:" << hex << m_CmdAuth;
    }
        break;
    case FREYALIB_CMD_PLUGINCHECK:
    {
//        if(0 == m_LastDataId.compare(data->GetProperty().toString(), Qt::CaseInsensitive))
//        {
//            m_LastDataId.clear();
//            m_PusherLoop->quit();
//        }
    }
        break;
    default:
    {
        if(m_CmdAuth & pData->command)
        {
            qDebug() << "FreyaLib > " << "Allow up command:" << hex << pData->command << "From:" << m_PluginID;
            FreyaData pReplyData = m_FreyaControl->FindFreyaData(pData->dataID);
            if(!pReplyData.isNull())
            {
                pReplyData->SetProperty(pData->GetProperty());
                if(!m_FreyaControl->ReplyExecution(pReplyData, this))
                {
                    qWarning() << "FreyaLib > " << "Bad reply";
                }
            }
            else
            {
                emit ToPluginRequest(pData);
            }
        }
        else
        {
            qDebug() << "FreyaLib > " << "Not allow up command:" << hex << pData->command << "From:" << m_PluginID;
        }
    }
        break;
    }
}

void FreyaPluginPusher::OnStateChanged(QLocalSocket::LocalSocketState state)
{
    switch(state)
    {
    case QLocalSocket::UnconnectedState:
    {
        qDebug()<<"FreyaLib > "<<"FreyaPluginPusher::OnStateChanged:"<<"UnconnectedState";
    }
        break;

    case QLocalSocket::ConnectingState:
    {
        qDebug()<<"FreyaLib > "<<"FreyaPluginPusher::OnStateChanged:"<<"ConnectingState";
    }
        break;

    case QLocalSocket::ConnectedState:
    {
        qDebug()<<"FreyaLib > "<<"FreyaPluginPusher::OnStateChanged:"<<"ConnectedState";
    }
        break;

    case QLocalSocket::ClosingState:
    {
        qDebug()<<"FreyaLib > "<<"FreyaPluginPusher::OnStateChanged:"<<"ClosingState";
    }
        break;
    default:
        break;
    }
}

void FreyaPluginPusher::OnConnected()
{
    FreyaData pData = FreyaBaseData::CreateDate(FREYALIB_CMD_EXTHOSTCONNECTED);
    pData->SetProperty(m_PluginID);
    m_FreyaControl->RequestExecution(pData, this);
}

void FreyaPluginPusher::OnDisconnected()
{
    FreyaData pData = FreyaBaseData::CreateDate(FREYALIB_CMD_EXTHOSTCUTED);
    pData->SetProperty(m_PluginID);
    m_FreyaControl->RequestExecution(pData, this);
}

///////////////

FreyaBaseExtension::FreyaBaseExtension(QString PlatformID, FreyaBaseControl *pControl, const char *actionName) :
    QLocalServer(), FreyaBaseAction(pControl, actionName), m_FreyaControl(pControl), m_isListening(false)
{
    qRegisterMetaType<QLocalSocket::LocalSocketState>("QLocalSocket::LocalSocketState");
    m_isListening = listen(PlatformID);
    connect(this, SIGNAL(newConnection()), SLOT(OnPluginRequest()), Qt::QueuedConnection);
    m_Thread = new QThread;
    moveToThread(m_Thread);
    m_Thread->start();
}

FreyaBaseExtension::~FreyaBaseExtension()
{
    m_Thread->terminate();
    m_Thread->wait();
    delete m_Thread;
}

bool FreyaBaseExtension::DefineAuthCode(const QStringList &MsgAuth, const QStringList &CmdAuth)
{
    for(int i = 0; i < qMin(MsgAuth.size(), 4); ++i)
    {
        FreyaData pData = FreyaBaseData::CreateDate();
        pData->dataID = MsgAuth.at(i);
        pData->SetProperty(qPow(2, i)); //0x01 0x02 0x04 0x08
        m_FreyaBaseControl->InsertFreyaData(pData);
    }
    for(int j = 0; j < qMin(CmdAuth.size(), 4); ++j)
    {
        FreyaData pData = FreyaBaseData::CreateDate();
        pData->dataID = CmdAuth.at(j);
        pData->SetProperty(qPow(2, j) * 0x10); //0x10 0x20 0x40 0x80
        m_FreyaBaseControl->InsertFreyaData(pData);
    }
    return m_isListening;
}

void FreyaBaseExtension::Execute(const FreyaData data)
{
    QMutexLocker locker(&m_PluginListMutex);
    if(!m_PusherList.isEmpty())
    {
        qDebug() << "FreyaLib > " << "Ext_Execution:" << "DataID:" << data->dataID << "Command:" << hex << data->command/* << dec << "Property:" << data->GetProperty()*/;
        QListIterator<FreyaPluginPusher*> PusherIT(m_PusherList);
        while(PusherIT.hasNext())
        {
            FreyaPluginPusher* pPusher = PusherIT.next();
            if(pPusher)
            {
                pPusher->PusherExecute(data);
            }
        }
    }
}

void FreyaBaseExtension::OnPluginRequest()
{
    QLocalSocket *plugin = nextPendingConnection();
    connect(plugin, SIGNAL(readyRead()), this, SLOT(OnReadyRead()), Qt::QueuedConnection);
}

void FreyaBaseExtension::OnReadyRead()
{
    QLocalSocket *plugin = qobject_cast<QLocalSocket *>(sender());
    if (plugin)
    {
        FreyaData data = FreyaBaseData::Unserialize(plugin->readAll());
        if(FREYALIB_CMD_PLUGINREQUEST == data->command)
        {
            qDebug() << "FreyaLib > " << "FreyaBaseExtension:" << hex << FREYALIB_CMD_PLUGINREQUEST;
            FreyaData resultData = FreyaBaseData::CreateDate();
            resultData->command = FREYALIB_CMD_PLUGINRESULT;
            QString PluginID = QUuid::createUuid().toString().toLower();
            resultData->SetProperty(PluginID);
            m_WaitPluginIDList.append(PluginID);
            plugin->write(FreyaBaseData::Serialize(resultData));
            plugin->waitForBytesWritten();
        }
        else if(FREYALIB_CMD_CONNECTREQUEST == data->command)
        {
            connect(plugin, SIGNAL(disconnected()), plugin, SLOT(deleteLater()));
            QString PluginID = data->GetProperty().toString().toLower();
            qDebug() << "FreyaLib > " << "FreyaBaseExtension:" << hex << FREYALIB_CMD_CONNECTREQUEST << PluginID;
            if(m_WaitPluginIDList.contains(PluginID))
            {
                m_WaitPluginIDList.removeOne(PluginID);
                FreyaPluginPusher* pPusher = new FreyaPluginPusher(PluginID, m_FreyaBaseControl);
                connect(pPusher, SIGNAL(ToPluginRequest(FreyaData)), this, SLOT(OnPuserRequest(FreyaData)), Qt::QueuedConnection);
                connect(pPusher, SIGNAL(ToDisconnected()), this, SLOT(OnPusherDisconnected()), Qt::QueuedConnection);
                m_PusherList.append(pPusher);
            }
            plugin->disconnectFromServer();
        }
    }
}

void FreyaBaseExtension::OnPusherDisconnected()
{
    QMutexLocker locker(&m_PluginListMutex);
    FreyaPluginPusher* pPusher = qobject_cast<FreyaPluginPusher*>(sender());
    if(pPusher && m_PusherList.removeOne(pPusher))
    {
        qDebug() << "FreyaLib > " << "PusherDisconnected:" << pPusher->FreyaPluginID();
        pPusher->deleteLater();
    }
}

void FreyaBaseExtension::OnPuserRequest(const FreyaData data)
{
    int waitTime = data->GetProperty(FREYALIB_FLG_WAITTIME).toInt();
    QString PluginId = data->GetProperty(FREYALIB_FLG_PLUGINID).toString();
    m_FreyaControl->RequestExecution(data, this, waitTime);
    if(waitTime > 0)
    {
        QMutexLocker locker(&m_PluginListMutex);
        if(!m_PusherList.isEmpty())
        {
            qDebug() << "FreyaLib > " << "Reply_Execution:" << "DataID:" << data->dataID << "Command:" << hex << data->command/* << dec << "Property:" << data->GetProperty()*/;
            QListIterator<FreyaPluginPusher*> PusherIT(m_PusherList);
            while(PusherIT.hasNext())
            {
                FreyaPluginPusher* pPusher = PusherIT.next();
                if(pPusher && 0 == PluginId.compare(pPusher->FreyaPluginID(), Qt::CaseInsensitive))
                {
                    pPusher->PusherExecute(data);
                    return;
                }
            }
        }
    }
}
